Supabase 개요

Supabase 기능 가이드

Part 1: 기본 구조 및 클라이언트 SDK

Firebase의 대안

1. 기본 아키텍처 구조

아키텍처 3계층 구성:

Pasted image 20250418131729.png

Client Application

주요 특징

SDK

주요 특징

Supabase Client SDK

import { createClient } from '@supabase/supabase-js'

// SDK 초기화
const supabase = createClient(
  'https://your-project.supabase.co',
  'your-anon-key'
)

// 기본 CRUD 작업 유틸리티
const DataService = {
  // 데이터 생성
  async createRecord(table: string, data: any) {
    const { data: result, error } = await supabase
      .from(table)
      .insert(data)
      .select()
    return { result, error }
  },

  // 데이터 조회
  async getRecords(table: string) {
    const { data, error } = await supabase
      .from(table)
      .select('*')
    return { data, error }
  },

  // 데이터 수정
  async updateRecord(table: string, id: number, data: any) {
    const { data: result, error } = await supabase
      .from(table)
      .update(data)
      .eq('id', id)
      .select()
    return { result, error }
  },

  // 데이터 삭제
  async deleteRecord(table: string, id: number) {
    const { error } = await supabase
      .from(table)
      .delete()
      .eq('id', id)
    return { error }
  }
}

Part 2: 서비스 계층

1. Auth (GoTrue)

주요 특징

구현 예시

const AuthService = {
  // 이메일 회원가입
  async signUp(email: string, password: string) {
    const { data, error } = await supabase.auth.signUp({
      email,
      password
    })
    return { data, error }
  },

  // 이메일 로그인
  async signIn(email: string, password: string) {
    const { data, error } = await supabase.auth.signInWithPassword({
      email,
      password
    })
    return { data, error }
  },

  // OAuth 로그인
  async signInWithProvider(provider: 'google' | 'github' | 'facebook') {
    const { data, error } = await supabase.auth.signInWithOAuth({
      provider
    })
    return { data, error }
  }
}

2. Storage

주요 특징

구현 예시

const StorageService = {
  // 파일 업로드
  async uploadFile(bucket: string, path: string, file: File) {
    const { data, error } = await supabase
      .storage
      .from(bucket)
      .upload(path, file) // 오브젝트 스토리지로 업로드(자주 바뀌지 않는 데이터)
    return { data, error }
  },

  // 파일 다운로드
  async downloadFile(bucket: string, path: string) {
    const { data, error } = await supabase
      .storage
      .from(bucket)
      .download(path)
    return { data, error }
  },

  // 파일 목록 조회
  async listFiles(bucket: string, path: string) {
    const { data, error } = await supabase
      .storage
      .from(bucket)
      .list(path)
    return { data, error }
  }
}

3. PostgREST

주요 특징

-- 데이터베이스 테이블 생성 
CREATE TABLE products (
  id SERIAL PRIMARY KEY,  -- 자동 증가하는 기본키
  name TEXT,             -- 제품명을 저장하는 텍스트 필드
  price INTEGER          -- 가격을 저장하는 정수 필드
);

-- 자동으로 생성되는 API 엔드포인트:
GET /products         -- 모든 제품 목록을 가져옴
POST /products        -- 새로운 제품을 데이터베이스에 추가
PATCH /products?id=eq.1  -- ID가 1인 제품의 정보를 수정
DELETE /products?id=eq.1  -- ID가 1인 제품을 삭제
-- 다중 테이블 조인
GET /orders?select=id,total,customers(name)&total=gt.1000
-- 주문 테이블에서 총액이 1000 초과인 주문을 조회하고,
-- 각 주문과 연관된 고객의 이름도 함께 가져옴

-- 서브쿼리 활용
GET /products?price=lt.(select.avg.price.from.products)
-- 전체 제품의 평균 가격보다 낮은 가격의 제품들만 조회
-- 주문과 관련 고객 정보를 한 번에 조회
GET /orders?select=id,order_date,customer:customers(*)
-- 주문 정보와 함께 해당 주문을 한 고객의 모든 정보를 한 번에 가져옴

-- 중첩 관계 조회
GET /categories?select=name,products(name,price,reviews(rating,comment))
-- 카테고리별로 속한 제품들의 정보와
-- 각 제품에 달린 리뷰 정보까지 계층적으로 조회
-- 다양한 필터링
GET /products?price=gt.1000&name=ilike.*phone*
-- 가격이 1000 초과이고 이름에 'phone'이 포함된 제품들을 조회

-- 복수 필드 정렬
GET /products?order=price.desc,name.asc
-- 제품을 가격 내림차순으로 정렬하고,
-- 같은 가격일 경우 이름 오름차순으로 정렬

-- 전체 텍스트 검색
GET /products?description=fts.smartphone
-- 설명에 'smartphone' 관련 단어가 포함된 제품을 전문 검색
-- 기본 페이지네이션
GET /products?limit=10&offset=20
-- 21번째부터 30번째 제품까지 10개의 결과를 반환

-- 커서 기반 페이지네이션
GET /products?order=created_at.desc&limit=10
-- 생성일 기준으로 최신 제품 10개를 조회
// Response Header: Content-Range: 0-9/100
-- 전체 100개 중 0-9번째 결과를 반환했다는 의미
-- RLS 정책 설정
CREATE POLICY "Users can only see their own data"
ON orders FOR SELECT
USING (auth.uid() = user_id);
-- 사용자가 자신의 주문 데이터만 볼 수 있도록 제한하는 정책 설정

-- 자동 쿼리 최적화
GET /products?select=name,category(name)
-- 제품명과 해당 제품의 카테고리명을 조회
// PostgREST가 자동으로 효율적인 JOIN 쿼리 생성
-- PostgREST가 내부적으로 최적화된 JOIN 쿼리를 생성하여 성능 향상

구현 예시

const APIService = {
  // 복잡한 관계형 쿼리
  async getComplexData() {
    const { data, error } = await supabase
      .from('posts')
      .select(`
        id,
        title,
        content,
        author:user_id(name, email),
        comments(
          id,
          content,
          user:user_id(name)
        )
      `)
      .eq('status', 'published')
      .order('created_at', { ascending: false })
      .range(0, 9)

    return { data, error }
  }
}
/* 변환되는 url로 http 요청 -> PostgREST가 이를 다시 SQL 쿼리로 변환
    GET https://[PROJECT_ID].supabase.co/rest/v1/posts
    ?select=id,title,content,author:user_id(name,email),comments(id,content,user:user_id(name))
    &status=eq.published
    &order=created_at.desc
    &offset=0
    &limit=9 
*/

4. Realtime Service

주요 특징

구현 예시

const RealtimeService = {
  // 테이블 변경사항 구독
  subscribeToChanges(table: string, callback: Function) {
    const subscription = supabase
      .channel(`public:${table}`)
      .on('postgres_changes',
        {
          event: '*',
          schema: 'public',
          table: table
        },
        (payload) => {
          callback(payload)
        }
      )
      .subscribe()

    return subscription
  },

  // 특정 레코드 구독
  subscribeToRecord(table: string, id: number, callback: Function) {
    const subscription = supabase
      .channel(`public:${table}:${id}`)
      .on('postgres_changes',
        {
          event: '*',
          schema: 'public',
          table: table,
          filter: `id=eq.${id}`
        },
        (payload) => {
          callback(payload)
        }
      )
      .subscribe()

    return subscription
  }
}

5. Edge Functions

주요 특징

구현 예시

// /functions/process-payment.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'

// 결제 처리 Edge Function
serve(async (req) => {
  const { amount, currency } = await req.json()

  // 결제 처리 로직
  const paymentResult = await processPayment(amount, currency)

  return new Response(
    JSON.stringify(paymentResult),
    { headers: { 'Content-Type': 'application/json' } }
  )
})

// 클라이언트 호출 유틸리티
const EdgeFunctionService = {
  async callEdgeFunction(functionName: string, body: any) {
    const { data, error } = await supabase.functions.invoke(functionName, {
      body: JSON.stringify(body)
    })
    return { data, error }
  }
}

Edge Functions 활용 사례

Part 3: Connection Pooler & Row Level Security

1. Connection Pooler (PgBouncer)

주요 특징

풀링 종류

※ Prepared Statement: SQL 쿼리의 템플릿을 미리 준비해두고 실행 시점에 파라미터만 바인딩하는 데이터베이스 기능

Transaction이나 Statement 모드에서는 연결이 지속적으로 재사용되므로 Prepared Statement를 유지할 수 없음

-- 쿼리 템플릿 준비
PREPARE user_query(text) AS
SELECT * FROM users WHERE name = $1;

-- 실제 실행 시 파라미터만 전달
EXECUTE user_query('John');

구현 예시

// Connection Pool 설정
const poolConfig = {
  mode: 'transaction',
  max: 10,
  min: 2,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000
}

const supabase = createClient('URL', 'ANON_KEY', {
  db: { poolConfig }
})

2. Row Level Security (RLS)

주요 특징

구현 예시

-- 기본 테이블 설정
CREATE TABLE private_documents (
  id SERIAL PRIMARY KEY,
  title TEXT,
  content TEXT,
  user_id UUID REFERENCES auth.users,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- RLS 활성화
ALTER TABLE private_documents ENABLE ROW LEVEL SECURITY;

-- 기본 정책들
CREATE POLICY "사용자 문서 조회" ON private_documents
FOR SELECT USING (auth.uid() = user_id);

CREATE POLICY "사용자 문서 생성" ON private_documents
FOR INSERT WITH CHECK (auth.uid() = user_id);

CREATE POLICY "사용자 문서 수정" ON private_documents
FOR UPDATE USING (auth.uid() = user_id);

-- 복잡한 정책 예시
CREATE POLICY "팀 문서 공유" ON team_documents
FOR SELECT USING (
  auth.uid() IN (
    SELECT user_id
    FROM team_members
    WHERE team_id = team_documents.team_id
  )
);

RLS 정책 유형